﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using UnityEditor;
using UnityEditor.Callbacks;
using UnityEditor.UIElements;
using UnityEngine;
using UnityEngine.UIElements;
using winS.Unity.Process;
using winS.UnityEditor.GUI;
using winS.UnityEditor.Undo;
using Object = UnityEngine.Object;
using TextAsset = UnityEngine.TextAsset;

namespace winS.UnityEditor.ProcessEditor
{
    /// <summary>
    /// 流程编辑器.
    /// 该编辑器可独立使用, 也可作为基类
    /// </summary>
    public class ProcessEditor : DataEditorWindow<ProcessEditorData>
    {
        private static readonly Dictionary<string, Type> processEditorDictionary = new Dictionary<string, Type>();

        public new virtual string title => "流程编辑器";

        public StepGraphView stepGraphView { get; private set; }
        public virtual Texture icon => Resources.Load<Texture>("winS.UnityEditor.ProcessEditor/Icon/Icon");

        internal UndoSystem undoSystem;
        internal GUIFactory guiFactory;

        protected override bool saveOnClose => false;
        protected EditorProcessInfo currentEditorProcessInfo;

        private Action onExitEditingState;
        private Action<EditorProcessInfo> onEnterEditingState;

        private InfoBar infoBar;
        private SettingBar settingBar;
        private SettingWindow settingWindow;
        private FileSettingWindow fileSettingWindow;
        private FileChangeWatcher fileChangeWatcher;

        static ProcessEditor()
        {
            Type processEditorType = typeof(ProcessEditor);
            foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
            {
                foreach (var type in assembly.GetTypes())
                {
                    if (!type.IsSubclassOf(processEditorType)) continue;
                    CustomProcessEditorAttribute customProcessEditorAttribute = type.GetCustomAttribute<CustomProcessEditorAttribute>();
                    if (customProcessEditorAttribute == null)
                    {
                        Debug.LogError($"{nameof(ProcessEditor)}: Type {type.Name} inherits from {nameof(ProcessEditor)} but has no attribute {nameof(CustomProcessEditorAttribute)}.");
                        continue;
                    }
                    processEditorDictionary.Add(customProcessEditorAttribute.extension, type);
                }
            }
            processEditorDictionary.Add(ProcessImporter.extension, processEditorType);
        }

        public static T OpenProcessEditorWindow<T>() where T : ProcessEditor
        {
            Type type = typeof(T);
            foreach (var window in Resources.FindObjectsOfTypeAll<T>())
            {
                if (window.GetType() == type)
                {
                    window.Show();
                    window.Focus();
                    return window;
                }
            }
            T instance = CreateInstance<T>();
            instance.Show();
            return instance;
        }
        public static bool OpenFile(string relativeProjectPath)
        {
            return Open(EditorAssetManager.LoadAsset<Object>(relativeProjectPath));
        }
        public static bool Open(Object processAssetObject)
        {
            EditorProcessInfo editorProcessInfo = LoadFromAsset(processAssetObject);
            if (editorProcessInfo == null) return false;
            Open(editorProcessInfo);
            return true;
        }
        /// <summary>
        /// Process管理，反射直接打开编辑器进程
        /// </summary>
        /// <param name="editorProcessInfo"></param>
        public static void Open(EditorProcessInfo editorProcessInfo)
        {
            (GetWindow(editorProcessInfo.editorType) as ProcessEditor).LoadProcess(editorProcessInfo);
        }
        public static EditorProcessInfo LoadFromAsset(Object obj)
        {
            if (!CheckObject(obj, out string path, out Type editorType)) return null;
            TextAsset textAsset = obj as TextAsset;
            if (!textAsset) return null;
            return LoadFromText(path, textAsset.text, editorType);
        }

        [OnOpenAsset(0)]
        private static bool OnOpenAsset(int instanceID, int line)
        {
            Object obj = EditorUtility.InstanceIDToObject(instanceID);
            if (!CheckObject(obj, out string path, out Type editorType))
            {
                return false;
            }
            EditorProcessInfo editorProcessInfo = LoadFromText(path, (obj as TextAsset).text, editorType);
            if (editorProcessInfo != null)
            {
                Open(editorProcessInfo);
            }
            return true;
        }
        private static bool CheckObject(Object obj, out string path, out Type editorType)
        {
            path = AssetDatabase.GetAssetPath(obj);
            string extension = Path.GetExtension(path);
            if (extension == string.Empty)//打开的是文件夹
            {
                editorType = default;
                return false;
            }
            extension = Path.GetExtension(path)[1..];
            if (!processEditorDictionary.TryGetValue(extension, out editorType))
            {
                return false;
            }
            return true;
        }
        private static EditorProcessInfo LoadFromText(string path, string text, Type editorType)
        {
            IProcess process;
            try { process = EditorProcessAssetManager.LoadFromString(text); }
            catch (Exception exception)
            {
                Debug.LogError($"{nameof(ProcessEditor)}.{nameof(LoadFromAsset)}: 加载流程失败, 原因是 {exception.Message}");
                return null;
            }
            return process == null ? null : new EditorProcessInfo(process, editorType, path);
        }

        protected override void OnCreate()
        {
            undoSystem = new UndoSystem();
            guiFactory = new GUIFactory(undoSystem);
            currentEditorProcessInfo = null;
            this.SetAntiAliasing(4);
            CreateWindow();
            fileChangeWatcher = new FileChangeWatcher(this);
            //注册事件
            onExitEditingState += infoBar.ExitEditingState;
            onExitEditingState += stepGraphView.ClearProcess;
            onExitEditingState += fileChangeWatcher.EndWatch;
            onExitEditingState += () => currentEditorProcessInfo = null;

            onEnterEditingState += infoBar.EnterEditingState;
            onEnterEditingState += stepGraphView.LoadProcess;
            onEnterEditingState += fileChangeWatcher.StartWatch;
            onEnterEditingState += newEditorProcessInfo => fileSettingWindow?.LoadProcessInfo(currentEditorProcessInfo);
            //监听事件
            fileChangeWatcher.onReload += Refresh;
            fileChangeWatcher.onDiscard += onExitEditingState;
            fileChangeWatcher.onRenamed += newPath => currentEditorProcessInfo.UpdatePath(newPath);

            AssemblyReloadEvents.afterAssemblyReload += TryOopenRecentProcess;
            EditorApplication.playModeStateChanged += PlayModeStateChangedCallback;

            if (EditorApplication.isPlayingOrWillChangePlaymode) TryOopenRecentProcess();
        }
        protected override void OnClose()
        {
            undoSystem.Clear();
            SaveEditorData();
            AssemblyReloadEvents.afterAssemblyReload -= TryOopenRecentProcess;
            EditorApplication.playModeStateChanged -= PlayModeStateChangedCallback;
            fileChangeWatcher.Dispose();
            if (editorData.saveOnClose) Save();
            OnCloseWindow();
        }

        public void OpenProcessFile(string pathRelativeProject)
        {
            if (pathRelativeProject == null) return;
            TextAsset textAsset = EditorAssetManager.LoadAsset<TextAsset>(pathRelativeProject);
            if (!textAsset) return;
            IProcess process = EditorProcessAssetManager.LoadFromString(textAsset.text);
            if (process == null) return;
            LoadProcess(new EditorProcessInfo(process, GetType(), pathRelativeProject));
        }

        protected void LoadProcess(EditorProcessInfo editorProcessInfo)
        {
            if (fileChangeWatcher.waitingForUserConfirmation)
            {
                EditorApplication.Beep();
                return;
            }

            if (editorData.saveOnSwitch) Save();
            onExitEditingState();
            onEnterEditingState(currentEditorProcessInfo = editorProcessInfo);
        }

        /// <summary>
        /// 编辑器保存后调用该方法
        /// </summary>
        protected virtual void OnSave()
        {
        }
        /// <summary>
        /// 编辑器刷新后调用该方法
        /// </summary>
        protected virtual void OnRefresh()
        {
        }
        /// <summary>
        /// 当窗口关闭后调用该方法.
        /// 该方法位于 OnDisable 消息下
        /// </summary>
        protected virtual void OnCloseWindow()
        {
        }
        /// <summary>
        /// 当窗口被创建后调用该方法.
        /// 该方法位于 OnEnable 消息下
        /// </summary>
        protected virtual void OnCreateWindow()
        {
        }

        protected override void OnSaveWindow()
        {
            if (currentEditorProcessInfo == null) return;
            if (fileChangeWatcher.waitingForUserConfirmation)
            {
                EditorApplication.Beep();
                return;
            }
            Save();
        }

        private void Save()
        {
            if (currentEditorProcessInfo != null)
            {
                fileChangeWatcher.EndWatch();
                stepGraphView.Save();
                EditorProcessAssetManager.Save(currentEditorProcessInfo);
                if (currentEditorProcessInfo.process.editorData.viewDataSaveTargetType == EditorData.DataSaveTargetType.SaveToLocal)
                {
                    ITransform transform = stepGraphView.contentViewContainer.transform;
                    editorData.fileViewData.Save(currentEditorProcessInfo, new ViewData(transform.scale.x, transform.position));
                }
                fileChangeWatcher.StartWatch(currentEditorProcessInfo);
            }
            OnSave();
        }
        private void CreateWindow()
        {
            titleContent = new GUIContent(title, icon);

            rootVisualElement.Add(infoBar = new InfoBar(editorData));
            rootVisualElement.Add(stepGraphView = new StepGraphView(this));
            rootVisualElement.Add(settingBar = new SettingBar(editorData));

            infoBar.history.onPing += PingProcess;
            infoBar.history.onOpen += OpenProcessFile;
            infoBar.filter.onTextChanged += stepGraphView.NodeFilter;
            infoBar.fileButton.onClick += OpenFileSettingWindow;
            infoBar.settingButton.onClick += OpenSettingWindow;
            infoBar.refreshButton.onClick += Refresh;
            settingBar.onSave += Save;

            OnCreateWindow();
        }
        private void Refresh()
        {
            if (currentEditorProcessInfo == null) return;
            EditorProcessInfo editorProcessInfo = currentEditorProcessInfo;
            editorProcessInfo.process = EditorProcessAssetManager.LoadFromString(EditorAssetManager.LoadAsset<TextAsset>(currentEditorProcessInfo.pathRelativeProject).text);
            onExitEditingState();
            onEnterEditingState(currentEditorProcessInfo = editorProcessInfo);
            OnRefresh();
        }
        private void PingProcess()
        {
            if (currentEditorProcessInfo == null) return;
            EditorAssetManager.PingAsset(currentEditorProcessInfo.pathRelativeProject);
        }

        private void OpenFileSettingWindow()
        {
            if (fileSettingWindow == null)
            {
                stepGraphView.Add(fileSettingWindow = new FileSettingWindow());
                rootVisualElement.schedule.Execute(() => fileSettingWindow.Show(currentEditorProcessInfo)).StartingIn(10);
                return;
            }
            if (fileSettingWindow.hidden) fileSettingWindow.Show(currentEditorProcessInfo);
            else fileSettingWindow.Hide();
        }
        private void OpenSettingWindow()
        {
            if (settingWindow == null) rootVisualElement.Add(settingWindow = new SettingWindow(editorData));
            settingWindow.style.display = DisplayStyle.Flex;
        }
        private void TryOopenRecentProcess()
        {
            OpenProcessFile(infoBar.history.GetRecentHistoryFilePath());
        }
        private void PlayModeStateChangedCallback(PlayModeStateChange playModeStateChange)
        {
            if (playModeStateChange == PlayModeStateChange.ExitingEditMode && editorData.saveOnClose) Save();
        }
    }
}